﻿#region 版权信息

//  ------------------------------------------------------------------------------
//   Copyright (C) 成都联宇创新科技有限公司 版权所有。 
//   
//   文件名：LotteryEngine.cs
//   文件功能描述：LotteryEngine.cs
//   创建标识：骆智慧 2014/07/16
//   
//   修改标识：
//   修改描述：
//  ------------------------------------------------------------------------------

#endregion

#region 名称空间导入

using System;
using System.Collections.Generic;
using System.Linq;
using Castle.ActiveRecord;
using Common.Logging;
using ECity.Domain;

#endregion

namespace ECity
{
    /// <summary>
    ///     抽奖引擎
    /// </summary>
    [Serializable]
    public class LotteryEngine : MarshalByRefObject
    {
        /// <summary>
        ///     未中奖平衡值（这个是基本算子，只要大于0就可以）
        /// </summary>
        private const int ProportionNotWin = 1;

        private readonly ILog _logger = LogManager.GetLogger<LotteryEngine>();

        /// <summary>
        ///     抽奖引擎构造
        /// </summary>
        /// <param name="sweepstake"></param>
        public LotteryEngine(LotterySweepstake sweepstake)
        {
            if (sweepstake == null)
            {
                throw new ArgumentException("指定活动无效！");
            }

            if (sweepstake.IsActived == 0)
            {
                throw new ArgumentException(sweepstake.Title + "还没有启动！");
            }

            sweepstake.RefreshConfig();

            var today = DateTime.Today;
            if (DateTime.Compare(sweepstake.StartDate, today) > 0)
            {
                throw new ArgumentException(sweepstake.Title + "还没开始");
            }

            if (DateTime.Compare(sweepstake.EndDate, today) < 0)
            {
                throw new ArgumentException(sweepstake.Title + "已经结束");
            }

            if (sweepstake.TodayActivedPrizeConfigs == null || sweepstake.TodayActivedPrizeConfigs.Count == 0)
            {
                throw new ArgumentException(sweepstake.Title + "\r\n未找到有效的奖项配置");
            }

            Sweepstake = sweepstake;

            var tmp = sweepstake.TodayActivedPrizeConfigs;
            if (tmp == null || !tmp.Any())
            {
                throw new ArgumentException(sweepstake.Title + "没有已经生效的奖项配置");
            }

            if (tmp.Count(x => x.IsByeItem == 1) < 1)
            {
                throw new ArgumentException(sweepstake.Title + "已经生效的奖项配置中必须有一个有效轮空项");
            }

            LotteryConfigs = tmp.ToList();

            foreach (var item in LotteryConfigs)
            {
                _logger.DebugFormat("配置ID={0},活动={1},奖项={2},奖品数量={3},剩余数量={4},中奖概率={5}%,开始={6},结束={7}"
                    , item.ID
                    , item.Sweepstake.Title
                    , item.LotteryPrize.PrizesName
                    , item.PublishCount
                    , item.PrizesBlance
                    , item.Probability*100.00
                    , item.StartDate.ToString("yyyyMMdd")
                    , item.EndDate.ToString("yyyyMMdd"));
            }
        }

        /// <summary>
        ///     随机码
        /// </summary>
        private string RandomSerial
        {
            get
            {
                var seed = GenerateRandomSeed();
                return Math.Abs(seed).ToString().PadRight(10, '0');
            }
        }

        /// <summary>
        ///     当前配置列表
        /// </summary>
        private List<LotteryConfig> LotteryConfigs { get; set; }

        /// <summary>
        ///     当前活动
        /// </summary>
        private LotterySweepstake Sweepstake { get; set; }

        /// <summary>
        ///     生成无重复的随机数种子
        /// </summary>
        public int GenerateRandomSeed()
        {
            var tick = Guid.NewGuid().GetHashCode();
            var seed = (int) (tick & 0xffffffffL) | tick >> 32;

            return seed;
        }

        /// <summary>
        ///     刷新各个活动赔率 init=true 初始化抽奖（奖品剩余数量复位） init=false 仅重新计算中奖概率
        /// </summary>
        public static void RefreshLotteryConfig(bool init = false)
        {
            var sweepstakes = LotterySweepstake.FindAll();

            Array.ForEach(sweepstakes, sweepstake =>
            {
                using (var scope = new TransactionScope())
                {
                    SweepstakesControl(sweepstake, init);

                    if (sweepstake.IsActived != 1 || sweepstake.AllPrizeConfigs.Count <= 0) return;
                    var groupedCfgs =
                        sweepstake.AllPrizeConfigs.GroupBy(
                            x =>
                                sweepstake.Code + "_" + x.StartDate.ToString("yyyyMMdd") + "_"
                                + x.EndDate.ToString("yyyyMMdd"));
                    foreach (var cfgs in groupedCfgs.Select(item => item.ToList()))
                    {
                        AutomaticBalanceTotalProbability(cfgs, balancedConfigs =>
                            balancedConfigs.ForEach(cfg =>
                            {
                                if (init)
                                {
                                    cfg.PrizesBlance = cfg.PublishCount;
                                    cfg.LotteryPrize.PrizeBlance = cfg.LotteryPrize.TotalCount;
                                    cfg.LotteryPrize.Update();
                                }
                                cfg.Update();
                            }));
                    }
                }
            });
        }

        /// <summary>
        ///     活动控制
        /// </summary>
        /// <param name="sweepstake"></param>
        /// <param name="init"></param>
        private static void SweepstakesControl(LotterySweepstake sweepstake, bool init)
        {
            sweepstake.AllPrizeConfigs
                .ToList()
                .ForEach(x =>
                {
                    if (init)
                    {
                        x.PrizesBlance = x.PublishCount;
                        x.LotteryPrize.PrizeBlance = x.LotteryPrize.TotalCount;
                    }
                    x.IsActived = sweepstake.IsActived;
                    x.LotteryPrize.IsActived = sweepstake.IsActived;
                    x.LotteryPrize.Update();
                    x.Update();
                });
        }

        /// <summary>
        ///     自动平衡各个奖项的概率
        ///     <remarks>建立圆桌算法，保证任意一奖项概率提高时自动压缩其余奖项开出概率，使总概率保持为1 </remarks>
        /// </summary>
        private static void AutomaticBalanceTotalProbability(List<LotteryConfig> lotteryConfigs,
            Action<List<LotteryConfig>> balanceTotalProbabilityCallback)
        {
            var doNotWin = lotteryConfigs
                .Find(x => x.IsByeItem == 1);

            doNotWin.Proportion = ProportionNotWin;

            //计算各奖项平衡值
            //公式  该奖项平衡值=  开出轮空项概率系数/该奖项的中奖概率系数 * 轮空项的平衡值
            lotteryConfigs.Where(cfgItem => cfgItem.IsByeItem == 0)
                .ToList()
                .ForEach(
                    cfgItem =>
                    {
                        cfgItem.Proportion = doNotWin.OddsCoefficient/cfgItem.OddsCoefficient*doNotWin.Proportion;
                    });

            //计算平衡值之和
            var sumProportion = lotteryConfigs.Sum(x => x.Proportion);

            //计算概率
            //公式 该奖项概率=该奖项平衡值/平衡值之和
            lotteryConfigs
                .ForEach(cfgItem =>
                {
                    cfgItem.Probability = cfgItem.Proportion/sumProportion;
                    cfgItem.Update();
                });


            balanceTotalProbabilityCallback(lotteryConfigs);
        }

        /// <summary>
        ///     基于TPLine 的转轮选择
        /// </summary>
        /// <param name="balancedConfigs">奖项配置列表</param>
        /// <param name="stopedPosition">模拟轮盘转轮运动停止后，指针指向的位置</param>
        /// <param name="wheelChiceCallback">转轮选择完成后的回调</param>
        private void BasedTpLineWheelChoice(List<LotteryConfig> balancedConfigs, double stopedPosition,
            Action<LotteryConfig> wheelChiceCallback)
        {
            var choiced = balancedConfigs.Find(x => x.IsByeItem == 1);
            for (var i = 0; i < LotteryConfigs.Count; i++)
            {
                var startPosition = ProbabilitySegmentLength(balancedConfigs, i);
                var endPosition = ProbabilitySegmentLength(balancedConfigs, i + 1);

                if (startPosition < stopedPosition && stopedPosition < endPosition)
                {
                    choiced = balancedConfigs[i];
                    break;
                }
            }

            wheelChiceCallback(choiced);
        }

        /// <summary>
        ///     计算奖项中奖概率之和
        /// </summary>
        /// <param name="lotteryCfglist"></param>
        /// <returns></returns>
        private static double SumProbabilities(IEnumerable<LotteryConfig> lotteryCfglist)
        {
            return lotteryCfglist.Sum(item => item.Probability);
        }

        /// <summary>
        ///     模拟转动轮盘
        /// </summary>
        /// <param name="balancedConfigs">平衡的奖项概率配置列表</param>
        /// <param name="simulatingStopedCallback">模拟轮盘运动停止时的回调代理</param>
        private void SimulatingRotationRoulette(List<LotteryConfig> balancedConfigs,
            Action<LotteryConfig> simulatingStopedCallback)
        {
            var probabilities = SumProbabilities(balancedConfigs);
            var seed = GenerateRandomSeed();
            // 用随机数模拟轮盘停止位置
            var simulatingStopedPosition = (new Random(seed).NextDouble())*probabilities;
            BasedTpLineWheelChoice(balancedConfigs, simulatingStopedPosition, simulatingStopedCallback);
        }

        /// <summary>
        ///     计算概率段长度
        /// </summary>
        /// <param name="balancedConfigs">奖项配置列表</param>
        /// <param name="toplimit">上限</param>
        /// <returns></returns>
        private static double ProbabilitySegmentLength(IList<LotteryConfig> balancedConfigs, int toplimit)
        {
            var theTempProbability = 0.00d;

            for (var i = 0; i < toplimit && i < balancedConfigs.Count; i++)
            {
                theTempProbability += balancedConfigs[i].Probability;
            }


            return theTempProbability;
        }

        /// <summary>
        ///     开始抽奖
        /// </summary>
        /// <param name="playRecord">抽奖引擎参数</param>
        /// <param name="eligibilityVerification">抽奖资格验证 true 可以参与抽奖 false 不能参与抽奖，永远不会中奖</param>
        /// <param name="lotteryCallback">回调参数</param>
        /// <returns></returns>
        public void StartTheLottery(LotteryRecord playRecord, Func<bool> eligibilityVerification,Action<LotteryRecord> lotteryCallback)
        {
            AutomaticBalanceTotalProbability(LotteryConfigs, balancedConfigs =>
            {
                var lotteryDoNotWin = balancedConfigs.Find(x => x.IsByeItem == 1);
                playRecord.Sweepstake = Sweepstake;
                playRecord.PlaySerial = RandomSerial;
                playRecord.PlayTime = DateTime.Now;

                playRecord.PlayResult = 0;
                playRecord.LotteryPrize = lotteryDoNotWin.LotteryPrize;

                if (Sweepstake.IsUpdating == 0 && eligibilityVerification())
                {
                    SimulatingRotationRoulette(balancedConfigs, playResult =>
                    {
                        if (playResult.IsByeItem == 0 && playResult.LotteryPrize.PrizeBlance > 0
                            && playResult.PrizesBlance > 0)
                        {
                            playRecord.PlayResult = 1;
                            playRecord.LotteryPrize = playResult.LotteryPrize;
                            playResult.PrizesBlance -= 1;
                            playResult.LotteryPrize.PrizeBlance -= 1;

                            playResult.LotteryPrize.Update();
                            playResult.Update();
                        }
                    });
                }
            });

            lotteryCallback(playRecord);
        }
    }
}